package taskTracker.ui.lite;

import java.util.Enumeration;
import java.util.Stack;
import java.util.Vector;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Ticker;
import javax.microedition.midlet.MIDlet;

import org.j4me.logging.Log;

import taskTracker.Project;
import taskTracker.Task;
import taskTracker.TimeEntries;
import taskTracker.util.Helper;
import taskTracker.util.Setting;

public class MainScreen extends MIDlet implements CommandListener {

	/**Milli seconds in a day. Used for calculations*/
	private static final long MILLIS_IN_DAY = 24*60*60*1000;

	/**Holds the projects stored in the program*/
	private Vector projects;

	/**Command to start logging item*/
	final Command cmdLog = new Command("Log time", Command.SCREEN, 5);
	
	/**Command that shows the daily report*/
	final Command cmdDailyReport = new Command("Daily report", Command.SCREEN, 6);

	/**Command that shows log messages*/
	final Command cmdDebug = new Command("Debug", Command.SCREEN, 6);

	/**Hides the program (minimize it)*/
	final Command cmdHide = new Command("Hide", Command.BACK, 6);

	/**Shows settings pagec*/
	final Command cmdSettings = new Command("Settings", Command.SCREEN, 8);

	/**Command that exits the program*/
	final Command cmdExit = new Command("Exit", Command.EXIT, 6);

	/**Display used to access mobile features*/
	public static Display display;

	/**Stores the screen that was shown before logging time/ showing status dialog*/
	private Stack previousScreen;
	
	/**Whether to vibrate the mobile when status is alerted*/
	private boolean vibratingAlert;

	/**Whether to play sound when status is alerted*/
	private boolean soundAlert;

	public MainScreen() {
		display = Display.getDisplay(this);
		Background.instance.setMainScreen(this);
		previousScreen = new Stack();
	}
	
    public void startApp() {
    	try {
	    	projects = Project.loadData();
	    	setSettings(Setting.loadSettings());
	    	if (previousScreen.isEmpty())
	    		displayScreen(displayProjects(projects));
	    	else
	    		goBack();
    	} catch (Exception e) {
    		Alert a = new Alert(e.toString(), "Error init:"+e.getMessage(), null, AlertType.ERROR);
    		a.setTimeout(Alert.FOREVER);
    		display.setCurrent(a);
    	}
    }

    public void pauseApp() {
    	
    }

    public void destroyApp(boolean unconditional) {
    	Project.saveData(projects);
    	saveCurrentScreen();
    	Setting.saveSettings(getSettings());
    	Background.instance.stop();
    	notifyDestroyed();
    }
    
    /**
     * This functions responds to all command actions in the whole program.
     */
	public void commandAction(Command cmd, Displayable disp) {
		try {
			// First, handle general commands
			if (cmd == this.cmdDailyReport) {
				viewDailyReport();
			} else if (cmd == this.cmdLog) {
				if (disp instanceof TaskListUI) {
					// XXX aseldawy There's a bug here.
					// This chooses active task before asking the user whether he needs to change active task or not
					TaskListUI taskList = (TaskListUI) disp;
					Task currentTask = (Task) taskList.getSelectedObject();
					if (currentTask != null && currentTask.isTrackable()) {
						Background.instance.setCurrentTask(currentTask);
					}
				}
				showLogTimeForm();
			} else if (cmd == this.cmdSettings) {
				displayScreen(getCategoryChooser(getSettings()));
			} else if (cmd == this.cmdDebug) {
				displayScreen(getLogViewer());
			} else if (cmd == this.cmdHide) {
				MainScreen.display.setCurrent(null);
				notifyPaused();
			} else if (cmd == this.cmdExit) {
				destroyApp(false);
			} else if (disp instanceof ProjectListUI) {
				// Handle commands for project list
				ProjectListUI projectList = (ProjectListUI) disp;
				Project selectedProject = (Project) projectList.getSelectedObject();
				if (cmd == projectList.cmdAdd) {
					displayScreen(getProjectForm(null));
				} else if (cmd == projectList.cmdEdit) {
					displayScreen(getProjectForm(selectedProject));
				} else if (cmd == projectList.cmdDelete) {
					Alert alert = new ConfirmationAlert(selectedProject.getName(), "Are you sure you want to delete?",
							projectList.getSelectedObject());
					alert.setCommandListener(this);
					displayScreen(alert);
				} else if (cmd == ProjectListUI.SELECT_COMMAND){
					// Select command for the list
					displayProject(selectedProject);
				}
			} else if (disp instanceof ProjectForm) {
				// Handle commands of project form
				ProjectForm projectForm = (ProjectForm) disp;
				if (cmd == projectForm.cmdCreate) {
					Project project = projectForm.getProject();
					// Add in model
					projects.addElement(project);
				} else if (cmd == projectForm.cmdUpdate) {
					projectForm.getProject();
				}
				goBack();
			} else if (disp instanceof TaskListUI) {
				TaskListUI taskList = (TaskListUI) disp;
				Task selectedTask = (Task) taskList.getSelectedObject();
				Project parentProject = taskList.getProject();
				if (cmd == TaskListUI.SELECT_COMMAND) {
					// The same as edit command
					Form taskForm = getTaskForm(parentProject, selectedTask);
					
					displayScreen(taskForm);
				} else if (cmd == taskList.cmdAdd) {
					// Show a form to add new task
					Form taskForm = getTaskForm(parentProject, null);
					displayScreen(taskForm);
				} else if (cmd == taskList.cmdEdit) {
					// Edit current task
					Form taskForm = getTaskForm(parentProject, selectedTask);
					displayScreen(taskForm);
				} else if (cmd == taskList.cmdBack) {
					// Back to list of projects
					goBack();
				} else if (cmd == taskList.cmdDelete) {
					// Delete current task (show an alert that will delete the task)
					Alert alert = new ConfirmationAlert(selectedTask.getName(), "Are you sure you want to delete?",
							taskList.getSelectedObject());
					alert.setCommandListener(this);
					displayScreen(alert);
				}
			} else if (disp instanceof TaskForm) {
				TaskForm taskForm = (TaskForm) disp;
				Project project = taskForm.getProject();
				if (cmd == taskForm.cmdCreate) {
					Task task = taskForm.getTask();
					project.addTask(task);
				} else if (cmd == taskForm.cmdUpdate) {
					taskForm.getTask();
				}
				goBack();
			} else if (disp instanceof ReportViewer) {
				ReportViewer reportViewer = (ReportViewer) disp;
				// Handle commands of ReportViewer
				if (cmd.getCommandType() == Command.BACK) {
					goBack();
				} else if (cmd == reportViewer.cmdRefresh) {
					reportViewer.refreshReport();
				}
			} else if (disp instanceof ConfirmationAlert) {
				// Handle commands for confirmation alert
				ConfirmationAlert cAlert = (ConfirmationAlert) disp;
				// This is deletion confirmation
				if (cAlert.confirmedObject instanceof Project) {
					Project deletedProject = (Project) cAlert.confirmedObject;
					if (cmd.getCommandType() == Command.OK) {
						// Delete from model
						this.projects.removeElement(deletedProject);
						// Remove its time entries
						deletedProject.removeTimeEntries();
					}
					// Display projects and select the project next to deleted one
					goBack();
				} else if (cAlert.confirmedObject instanceof Task) {
					// Find the task that need to be deleted
					Task task = (Task) cAlert.confirmedObject;
					Project project = task.getParent();
					if (cmd.getCommandType() == Command.OK) {
						// Remove it from model
						project.removeTask(task);
					}
					// Redisplay the list of tasks
					goBack();
				}
			} else if (disp instanceof LogTimeForm) {
				LogTimeForm logTimeForm = (LogTimeForm) disp;
				if (cmd == logTimeForm.cmdOk) {
					// Start logging
					Background.instance.startLogging(logTimeForm.getTask(), logTimeForm.getComment(), logTimeForm.getInterval());
					logTimeForm.hide();
					goBack();
				} else if (cmd == logTimeForm.cmdCancel) {
					// Stop logging time
					Background.instance.setStatusAlertInterval(logTimeForm.getInterval());
					logTimeForm.hide();
					goBack();
				}
			} else if (disp instanceof StatusAlert) {
				StatusAlert statusAlert = (StatusAlert) disp;
				if (cmd == statusAlert.cmdStart) {
					Background.instance.startLogging(statusAlert.getTask(), statusAlert.getComment(), statusAlert.getInterval());
					goBack();
				} else if (cmd == statusAlert.cmdStop) {
					Background.instance.stopLogging();
					this.showLogTimeForm();
				} else if (cmd == statusAlert.cmdHide) {
					Background.instance.setCurrentTask(statusAlert.getTask());
					Background.instance.setComment(statusAlert.getComment());
					Background.instance.setStatusAlertInterval(statusAlert.getInterval());
					goBack();
				}
			} else if (disp instanceof LogViewer) {
				if (cmd.getCommandType() == Command.BACK) {
					goBack();
				}
			} else if (disp instanceof CategoryChooser) {
				if (cmd == CategoryChooser.SELECT_COMMAND) {
					CategoryChooser categoryChooser = (CategoryChooser) disp;
					String category = (String)categoryChooser.getSelectedObject();
					displayScreen(getSettingsPage(category, categoryChooser
							.getSettings()));
				} else if (cmd.getCommandType() == Command.BACK) {
					goBack();
				}
			} else if (disp instanceof SettingsPage) {
				if (cmd.getCommandType() == Command.BACK) {
					SettingsPage settingsPage = (SettingsPage) disp;
					// Apply settings in this page so that it in effect in next screen
					settingsPage.applySettings();
					setSettings(settingsPage.getSettings());
					goBack();
				}
			}

		} catch (Exception e) {
			Helper.displayError("ProjectListUI#commandAction", e);
		}
	}

	/**
	 * Returns a menu that can select from different settings categories
	 * @return
	 */
	private Displayable getCategoryChooser(Setting[] settings) {
		CategoryChooser cc = new CategoryChooser(settings);
		cc.setTicker(createStatusTicker());
		cc.addCommand(cmdExit);
		cc.addCommand(cmdDebug);
		cc.setCommandListener(this);
		return cc;
	}
	
	/**
	 * Returns a page to edit settings under the specified category
	 * @param category
	 * @param settings
	 * @return
	 */
	private SettingsPage getSettingsPage(String category, Setting[] settings) {
		SettingsPage settingsPage = new SettingsPage(category, settings);
		settingsPage.setTicker(createStatusTicker());
		settingsPage.setCommandListener(this);
		settingsPage.addCommand(cmdExit);
		return settingsPage;
	}

	/**
	 * 
	 * @return
	 */
	private Setting[] getSettings() {
		return new Setting[] {
			new Setting("Quote frequency", "Random", "multiple\nRandom\nHourly\nDaily\nWeekly", "General"),	
			new Setting("Vibration", new Boolean(vibratingAlert), "Boolean", "General"),	
			new Setting("Sound", new Boolean(soundAlert), "Boolean", "General"),	
			new Setting("Font size", new Integer(1), "Integer", "Display"),
			new Setting("Screens", previousScreen, "hidden", "Display"),
		};
	}

	/**
	 * Set settings
	 * @param settings
	 */
	private void setSettings(Setting[] settings) {
		if (settings == null)
			return;
		for (int i = 0; i < settings.length; i++) {
			if (settings[i].name.equals("Screens")) {
				this.previousScreen = (Stack) settings[i].value;
			} else if (settings[i].name.equals("Vibration")) {
				this.vibratingAlert = ((Boolean) settings[i].value).booleanValue();
			} else if (settings[i].name.equals("Sound")) {
				this.soundAlert = ((Boolean) settings[i].value).booleanValue();
			}
		}
	}

	/**
	 * Displays the given displayable.
	 * Saves current screen before changing the display.
	 * @param displayable
	 */
	private void displayScreen(Displayable nextDisplayable) {
		saveCurrentScreen();
		MainScreen.display.setCurrent(nextDisplayable);
	}
	
	private void saveCurrentScreen() {
		String encodedScreen = saveScreen();
		if (encodedScreen != null)
			previousScreen.push(encodedScreen);
	}
	
	/**
	 * Goes back one screen from saved history
	 */
	private void goBack() {
		MainScreen.display.setCurrent(restoreScreen((String) previousScreen.pop()));
	}
	
	/**
	 * Save current screen in an instance variable to be able to restore it later
	 */
	private String saveScreen() {
		String currentScreen = null;
		if (MainScreen.display.getCurrent() instanceof ProjectListUI) {
			// encode it as "project/{project_index}"
			Project project = (Project) ((ProjectListUI)MainScreen.display.getCurrent()).getSelectedObject();
			currentScreen = "project/"+projects.indexOf(project);
		} else if (MainScreen.display.getCurrent() instanceof TaskListUI) {
			// encode it as "task/{project_index}/{task_index}"
			TaskListUI taskList = (TaskListUI)MainScreen.display.getCurrent();
			Project project = taskList.getProject();
			currentScreen = "task/"+projects.indexOf(project)+"/"+taskList.getSelectedIndex();
		} else if (MainScreen.display.getCurrent() instanceof StatusAlert) {
			// do nothing, keep previousScreen as is
		} else if (MainScreen.display.getCurrent() instanceof LogTimeForm) {
			LogTimeForm logTimeForm = (LogTimeForm) MainScreen.display.getCurrent();
			logTimeForm.hide();
		} else if (MainScreen.display.getCurrent() instanceof LogViewer) {
			currentScreen = "debug";
		} else if (MainScreen.display.getCurrent() instanceof CategoryChooser) {
			CategoryChooser categoryChooser = (CategoryChooser) MainScreen.display.getCurrent();
			currentScreen = "settings_categories/" + categoryChooser.getSelectedIndex();
		} else if (MainScreen.display.getCurrent() instanceof SettingsPage) {
			SettingsPage settingsPage = (SettingsPage) MainScreen.display.getCurrent();
			// Apply settings in this page so that it in effect in next screen
			settingsPage.applySettings();
			setSettings(settingsPage.getSettings());
			currentScreen = "settings_page/" + settingsPage.getTitle();
		}
		return currentScreen;
	}

	/**
	 * Restores a previously stored screen to the state it was.
	 * @param encoded
	 * @return
	 */
	private Displayable restoreScreen(String encoded) {
		String[] parts = Helper.split(encoded, "/");
		Displayable restoredScreen = null;
		if (parts[0].equals("project")) {
			ProjectListUI projectList = displayProjects(projects);
			// highlight selected project
			int projectIndex = Integer.parseInt(parts[1]);
			// Check index because this project may have been deleted
			if (projectIndex < projectList.size() && projectIndex >= 0)
				projectList.setSelectedIndex(projectIndex, true);
			restoredScreen = projectList;
		} else if (parts[0].equals("task")) {
			int projectIndex = Integer.parseInt(parts[1]);
			TaskListUI taskList = displayProject((Project) projects.elementAt(projectIndex));
			// highlight selected task
			int taskIndex = Integer.parseInt(parts[2]);
			// Check index because this task may have been deleted
			if (taskIndex > 0 && taskIndex < taskList.size())
				taskList.setSelectedIndex(taskIndex, true);
			restoredScreen = taskList;
		} else if (parts[0].equals("debug")) {
			restoredScreen = getLogViewer();
		} else if (parts[0].equals("settings_categories")) {
			restoredScreen = getCategoryChooser(getSettings());
			((CategoryChooser) restoredScreen).setSelectedIndex(
					Integer.parseInt(parts[1]), true);
		} else if (parts[0].equals("settings_page")) {
			restoredScreen = getSettingsPage(parts[1], getSettings());
		} else {
			System.err.println("undefined screen: "+previousScreen);
		}
		return restoredScreen;
	}
	
	private Displayable getLogViewer() {
		LogViewer logViewer = new LogViewer(Log.getLogMessages());
		logViewer.setCommandListener(this);
		return logViewer;
	}

	/**
	 * Creates a ticker that displays current status.
	 * @return
	 */
	private Ticker createStatusTicker() {
		String tickerString = "";
		if (Background.instance.isLogging()) {
			tickerString += "Total ("+Helper.formatTime(Background.instance.getTotalTime())+") ";
			tickerString += "working ";
			Task currentTask = Background.instance.getCurrentTask();
			tickerString += currentTask.getParent().getName() + "/" + currentTask.getName();
			tickerString += "("+Background.instance.getComment()+"). ";
		} else {
			tickerString += "Idle ";
			tickerString += "for ("+Helper.formatTime(Background.instance.getTotalTime())+") ";
		}
		return new Ticker(tickerString);
	}
	
	/**
	 * Returns a new instance of ProjectListUI that displays current projects.
	 * @param projects
	 */
	private ProjectListUI displayProjects(Vector projects) {
		ProjectListUI projectList = new ProjectListUI(projects);
		projectList.addCommand(cmdDailyReport);
		projectList.addCommand(cmdHide);
		projectList.addCommand(cmdSettings);
		projectList.addCommand(cmdExit);
		projectList.addCommand(cmdLog);
		projectList.addCommand(cmdDebug);
		projectList.setCommandListener(this);
		projectList.setTicker(createStatusTicker());
		
		return projectList;
	}

	/**
	 * Creates or retrieves a project form showing the data of the given project
	 */
	private Form getProjectForm(Project project) {
		// Create the form
		ProjectForm projectForm = new ProjectForm(project);
		projectForm.setTicker(createStatusTicker());
		projectForm.setCommandListener(this);
		return projectForm;
	}

	/**
	 * Returns a new instance of ProjectListUI that displays current projects.
	 * @param projects
	 */
	private TaskListUI displayProject(Project project) {
		TaskListUI taskList = new TaskListUI(project.getTasks(), project);
		taskList.setTitle(project.toString());
		taskList.addCommand(cmdDailyReport);
		taskList.addCommand(cmdExit);
		taskList.addCommand(cmdLog);
		taskList.addCommand(cmdDebug);
		taskList.setCommandListener(this);
		taskList.setTicker(createStatusTicker());
		displayScreen(taskList);
		return taskList;
	}
	
	/**
	 * Creates or retrieves a task list form
	 */
	private Form getTaskForm(Project project, Task task) {
		TaskForm taskForm = new TaskForm(project, task);
		taskForm.setTicker(createStatusTicker());
		taskForm.setCommandListener(this);
		return taskForm;
	}

	/**
	 * View report screen initialized to report for today.
	 */
	private void viewDailyReport() {
		ReportViewer rv = new ReportViewer("Daily report", projects);
		rv.setTicker(createStatusTicker());
		rv.setCommandListener(this);
		long now = System.currentTimeMillis();
		long start = now - now % MILLIS_IN_DAY;
		long end = start + MILLIS_IN_DAY;
		rv.prepare(start, end, null);
		displayScreen(rv);
	}

	public void showStatusAlert() {
		Log.debug("Preparing status alert");
		// Add top tasks to GUI
		Task[] topTasks = TimeEntries.getInstance().getLatestTasks(5);
		Log.debug("Top tasks ready of size (before): "+topTasks.length);
		if (topTasks.length < 5) {
			Vector allTasks = new Vector();
			outer_loop:
			for (Enumeration e = projects.elements();e.hasMoreElements();) {
				Project p = (Project) e.nextElement();
				for (Enumeration f = p.getTasks().elements(); f.hasMoreElements();) {
					Task task = (Task) f.nextElement();
					if (task.isTrackable()) {
						allTasks.addElement(task);
						if (allTasks.size() >= 5) {
							break outer_loop;
						}
					}
				}
			}
			topTasks = new Task[allTasks.size()];
			allTasks.copyInto(topTasks);
		}
		Log.debug("Top tasks ready of size (after): "+topTasks.length);
		StatusAlert statusAlert = new StatusAlert();
		Log.debug("Status alert created");
		statusAlert.prepare(topTasks, Background.instance.getComment(), Background.instance.isLogging(),
				Background.instance.getCurrentTask(), Background.instance.getStatusAlertInterval());
		statusAlert.setCommandListener(this);

		Log.debug("statusAlert prepared");
		displayScreen(statusAlert);
		Log.debug("statusAlert displayed");
		MainScreen.display.callSerially(new Runnable() {
			
			public void run() {
				MainScreen.display.vibrate(500);
				MainScreen.display.flashBacklight(500);
			}
		});
	}

	/**
	 * Prepares and shows the form that asks the user to log time
	 */
	public void showLogTimeForm() {
		if (Background.instance.isLogging()) {
			// If user is currently logging time, show status alert instead
			showStatusAlert();
		} else {
			LogTimeForm logTimeForm = new LogTimeForm(this.projects, Background.instance.getCurrentTask());
			logTimeForm.prepare(Background.instance.getComment(), Background.instance.isLogging(),
					Background.instance.getCurrentTask(), Background.instance.getStatusAlertInterval());
			logTimeForm.setCommandListener(this);
			displayScreen(logTimeForm);
		}
	}

}
